using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using GrapeCity.ActiveAnalysis.DataSources;
using GrapeCity.ActiveAnalysis.Schema;

namespace GrapeCity.ActiveAnalysis.Samples
{
	/// <summary>
	/// Maintains the list of named data sources.
	/// </summary>
	public sealed class DataSourceCatalog : IDisposable
	{
		private readonly List<NamedDataSource> _dataSources;
		private IList<string> _names;
		private string _folder;
		private readonly bool _initializing;
		private static readonly object _mutex = new object();

		/// <summary>
		/// ActiveAnalysis data source file extension
		/// </summary>
		internal const string FileExtension = "ddads";

		/// <summary>
		/// Gets the default path to load/save the data sources to
		/// </summary>
		const string configKey = "DataSourcesPath";
		internal static readonly string DataSourcesPath = System.Configuration.ConfigurationManager.AppSettings[configKey];
		[System.Runtime.InteropServices.DllImport("Shfolder.dll")]
		private static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken, int dwFlags, StringBuilder lpszPath);

		static DataSourceCatalog()
		{
			try
			{
                //StringBuilder comDoc = new StringBuilder(260);
                //SHGetFolderPath(IntPtr.Zero, 0x002e, IntPtr.Zero, 0, comDoc);
                //string s = comDoc.ToString();
                string datsourc = @"DataSources";
                string ds = Directory.GetCurrentDirectory() + @"\" + datsourc;
                if (!Directory.Exists(ds)) Directory.CreateDirectory(ds);
				DataSourcesPath = Path.Combine(Directory.GetCurrentDirectory()//comDoc.ToString()
                    , datsourc);
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
			}
		}

		internal static readonly Dictionary<string, DataSourceCatalog> Catalogs = 
			new Dictionary<string, DataSourceCatalog>();

		/// <summary>
		/// Gets an instance of the <see cref="DataSourceCatalog"/> for the specified folder with data sources' files.
		/// </summary>
		/// <param name="folder"></param>
		/// <returns></returns>
		public static DataSourceCatalog GetInstance(string folder)
		{
			lock (_mutex)
			{
				if (string.IsNullOrEmpty(DataSourcesPath))
					throw new InvalidOperationException("My Documents is not available.");

				if (string.IsNullOrEmpty(folder))
					folder = DataSourcesPath;

				if (!Catalogs.ContainsKey(folder))
					Catalogs.Add(folder, new DataSourceCatalog(folder));

				return Catalogs[folder];
			}
		}

		private DataSourceCatalog(string folderPath)
		{
			if (string.IsNullOrEmpty(folderPath))
				throw new ArgumentNullException("folderPath");

			if (!Directory.Exists(folderPath))
				throw new ArgumentException("Folder is not found: " + folderPath);

			_dataSources = new List<NamedDataSource>();

			_initializing = true;
			InitFromFolder(folderPath);
			_initializing = false;
		}


		#region Public methods

		/// <summary>
		/// Adds new data source to the catalog
		/// </summary>
		/// <param name="name"></param>
		/// <param name="dataSource"></param>
		public void AddDataSource(string name, IDataSource dataSource)
		{
			if (_dataSources.Exists(new NamedDataSourcePredicate(name).Predicate))//delegate(NamedDataSource ds) { return ds.Name == name; }))
				throw new ArgumentException("DataSource with that name is already added", "name");
			if (dataSource == null)
				throw new ArgumentNullException("dataSource");

			NamedDataSource namedDataSource = new NamedDataSource(name, dataSource);
			AddDataSourceImpl(namedDataSource);
		}

		private class NamedDataSourcePredicate
		{
			private readonly string _dsName;
			public NamedDataSourcePredicate(string dsName)
			{
				_dsName = dsName;
			}
			public bool Predicate(NamedDataSource ds)
			{
				return ds.Name == _dsName;
			}
		}

		/// <summary>
		/// Removes data source from collection, optionally removing it from folder
		/// </summary>
		/// <param name="name"></param>
		public void RemoveDataSource(string name)
		{
			NamedDataSource dataSource =
				_dataSources.Find(new NamedDataSourcePredicate(name).Predicate);//delegate(NamedDataSource ds) { return ds.Name == name; });

			if (dataSource == null)
				throw new ArgumentException(string.Format("Failed to find data source named '{0}'", name));

			RemoveDataSourceImpl(dataSource);

			dataSource.Instance.Disconnect();
		}

		/// <summary>
		/// Gets the names of catalog items
		/// </summary>
		[Browsable(false)]
		public IList<string> Names
		{
			get
			{
				if (_names == null)
				{
					Converter<NamedDataSource, string> namedDataSourceConvertor = delegate(NamedDataSource ds)
						{
							return ds.Name;
						};
					List<string> names = _dataSources.ConvertAll(namedDataSourceConvertor);
					names.Sort();
					_names = names.AsReadOnly();
				}
				return _names;
			}
		}

		/// <summary>
		/// Gets list of data sources
		/// </summary>
		internal IEnumerable<NamedDataSource> DataSources
		{
			get { return _dataSources; }
		}

		/// <summary>
		/// Gets the name of the specified data source from the catalog.
		/// </summary>
		/// <param name="dataSource">The data source to find name.</param>
		/// <returns>The name of the shared data source.</returns>
		public string GetDataSourceName(IDataSource dataSource)
		{
			foreach (NamedDataSource namedDataSource in DataSources)
			{
				if (namedDataSource.Instance == dataSource)
					return namedDataSource.Name;
			}
			return null;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Disconnect all data sources.
		/// </summary>
		public void Dispose()
		{
			// we might decide to not to disconnect in case it was connected already
			foreach (NamedDataSource ds in _dataSources)
			{
				ds.Instance.Disconnect();
			}

			_dataSources.Clear();
		}

		#endregion

		/// <summary>
		/// Resolve data source by its shared name.
		/// </summary>
		/// <param name="name">Shared data source id.</param>
		/// <returns>Resolved data source.</returns>
		public IDataSource Resolve(string name)
		{
			NamedDataSource dataSource =
				_dataSources.Find(new NamedDataSourcePredicate(name).Predicate);//delegate(NamedDataSource ds) { return ds.Name == name; });

			return dataSource == null ? null : dataSource.Instance;
		}

		#region Implementation

		/// <summary>
		/// Loads all data sources from specific folder
		/// </summary>
		/// <param name="folderPath"></param>
		private void InitFromFolder(string folderPath)
		{
			if (Names.Count > 0)
				throw new InvalidOperationException("Initializing is not allowed when any datasource is specified");

			foreach (string fileName in Directory.GetFiles(folderPath, "*." + FileExtension))
			{
				try
				{
					NamedDataSource dataSource;
					LoadDataSource(fileName, out dataSource);

					if (dataSource == null || string.IsNullOrEmpty(dataSource.Name) || dataSource.Instance == null)
						throw new DataSourceConfigurationException("", "Incorrect file format");

					FixupRelativePaths(folderPath, dataSource.Instance);

					AddDataSourceImpl(dataSource);
					dataSource.Tag = fileName;
				}
				catch (ArgumentException e)
				{
					Trace.WriteLine(e);
				}
				catch (IOException e)
				{
					Trace.WriteLine(e);
				}
				catch (XmlException e)
				{
					Trace.WriteLine(e);
				}
			}

			_folder = folderPath;
		}

		private static void FixupRelativePaths(string rootPath, IDataSource instance)
		{
			lock (_mutex)
			{
				if (instance == null || instance.IsConnected)
					throw new ArgumentException("Data source is not specified or has invalid state");

				LocalCubeDataSource lcds = instance as LocalCubeDataSource;
				if (lcds != null)
				{
					if (!Path.IsPathRooted(lcds.LocalCubeFile))
						lcds.LocalCubeFile = Path.Combine(rootPath, lcds.LocalCubeFile);
				}

				RdDataSource rdds = instance as RdDataSource;
				if (rdds != null)
				{
					if (rdds.ConnectionType == ConnectionType.OleDb &&
						rdds.ConnectionString.Contains("Microsoft.Jet.OLEDB.4.0"))
					{
						// HACK - fix data source path for standard data sources
						// Note - it is impossible to fix related pathes for all data source types
						// because connection string hasn't the same structure.
						rdds.ConnectionString = FixupRelatedPathInConnectionString(rootPath, rdds.ConnectionString);
					}
					if (!Path.IsPathRooted(rdds.CustomSchemaFile))
						rdds.CustomSchemaFile = Path.Combine(rootPath, rdds.CustomSchemaFile);
				}

				XmlDataSource xmlds = instance as XmlDataSource;
				if (xmlds != null)
				{
					if (!Path.IsPathRooted(xmlds.CustomSchemaFile))
						xmlds.CustomSchemaFile = Path.Combine(rootPath, xmlds.CustomSchemaFile);
					if (!Path.IsPathRooted(xmlds.DataFile))
						xmlds.DataFile = Path.Combine(rootPath, xmlds.DataFile);
					if (!string.IsNullOrEmpty(xmlds.TransformationFile) &&
						!Path.IsPathRooted(xmlds.TransformationFile))
						xmlds.TransformationFile = Path.Combine(rootPath, xmlds.TransformationFile);
				}
			}
		}

		static readonly Regex DataSourcePattern = new Regex(@"(?<=data\ssource=)[^;]+",
			RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace);
		private static string FixupRelatedPathInConnectionString(string rootPath, string connectionString)
		{
			string dataSourcePath = DataSourcePattern.Match(connectionString).Value;
			if (!string.IsNullOrEmpty(dataSourcePath) && !Path.IsPathRooted(dataSourcePath))
			{
				connectionString = connectionString.Replace(
					dataSourcePath, Path.Combine(rootPath, dataSourcePath));
			}

			return connectionString;
		}

		private const string RootTag = "DataSourceFile";

		private static void LoadDataSource(string fileName, out NamedDataSource dataSource)
		{
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.IgnoreComments = true;
			settings.IgnoreWhitespace = true;
			settings.CloseInput = true;
			using (XmlReader reader = XmlReader.Create(fileName, settings))
			{
				reader.MoveToContent();
				reader.ReadStartElement(RootTag);
				dataSource = new NamedDataSource(reader);
				reader.ReadEndElement();
			}
		}

		internal static void SaveDataSource(string fileName, NamedDataSource dataSource)
		{
			if (string.IsNullOrEmpty(fileName))
				throw new ArgumentNullException("fileName");
			if (dataSource == null)
				throw new ArgumentNullException("dataSource");

			XmlWriterSettings settings = new XmlWriterSettings();
			settings.Indent = true;
			settings.IndentChars = "\t";
			settings.Encoding = new UTF8Encoding(false);
			settings.CloseOutput = true;
			using (XmlWriter writer = XmlWriter.Create(fileName, settings))
			{
				if (writer == null)
					throw new ArgumentException("Invalid file.", fileName);
				writer.WriteStartElement(RootTag);
				dataSource.Write(writer);
				writer.WriteEndElement();
			}
			dataSource.Tag = fileName;
		}

		internal static string GenerateFileName(string folderPath, string dataSourceName)
		{
			string result;

			try
			{
				string fileName = dataSourceName.Replace("/", "").
					Replace(@"\", "").
					Replace("..", " ").
					Trim();

				StringBuilder validFileName = new StringBuilder();

				// remove all non-alphanumeric characters
				string validCharacters = " -=[].\"'{{}}~!@#$%^&()_+|";
				foreach (char c in fileName)
				{
					if (char.IsLetterOrDigit(c) || validCharacters.IndexOf(c) >= 0)
						validFileName.Append(c);
				}

				//if name doesn't contains alphanumeric characters
				if (validFileName.Length == 0)
					validFileName.Append("DataSource");

				int num = 0;
				do
				{
					num++;
					string postFix = num == 1 ? "" : num.ToString();
					result = Path.Combine(folderPath, validFileName + postFix + "." + FileExtension);
				} while (File.Exists(result));
			}
			catch
			{
				result = Guid.NewGuid().ToString();
			}
			return result;
		}

		internal void RemoveDataSourceImpl(NamedDataSource dataSource)
		{
			_dataSources.Remove(dataSource);
			_names = null;

			string fileName = (string)dataSource.Tag;
			if (!File.Exists(fileName))
				throw new InvalidOperationException(string.Format(
					"The file named '{0}' that keeps settings for '{1}' data source is not found",
					fileName, dataSource.Name));
			File.Delete(fileName);
		}

		internal void AddDataSourceImpl(NamedDataSource dataSource)
		{
			_dataSources.Add(dataSource);
			_names = null;

			if (!_initializing)
			{
				string fileName = GenerateFileName(_folder, dataSource.Name);
				SaveDataSource(fileName, dataSource);
			}
		}

		#endregion

		#region Unbound data source stuff

		/// <summary>
		/// Creates unbound data source.
		/// </summary>
		/// <param name="input"></param>
		/// <returns></returns>
		public static IDataSource CreateUnboundDataSource(string input)
		{
			UnboundDataSource ds = new UnboundDataSource();
			ds.CustomSchema = PrepareSchema();
			ds.DataSource = PrepareDataSet();
			return ds;
		}

		private static DataSet PrepareDataSet()
		{
			DataSet dataset = new DataSet("TestingPubsTitles");
			DataTable table = dataset.Tables.Add("TestingPubsTitles");
			table.Columns.Add("title_id", typeof(string));
			table.Columns.Add("title", typeof(string));
			table.Columns.Add("type", typeof(string));
			table.Columns.Add("pub_id", typeof(string));
			table.Columns.Add("price", typeof(decimal));
			table.Columns.Add("advance", typeof(decimal));
			table.Columns.Add("royalty", typeof(int));
			table.Columns.Add("ytd_sales", typeof(int));
			table.Columns.Add("notes", typeof(string));
			table.Columns.Add("pubdate_str", typeof(DateTime));
			table.Columns.Add("au_id", typeof(string));
			table.Columns.Add("au_lname", typeof(string));
			table.Columns.Add("au_fname", typeof(string));

			table.Rows.Add("BU1032", "The Busy Executive's Database Guide", "business    ", "1389", 19.99, 5000.00, 10, 4095, "An overview of available database systems with emphasis on common business applications. Illustrated.", new DateTime(1991, 06, 12), "409-56-7008", "Green", "Marjorie");
			table.Rows.Add("BU1032", "The Busy Executive's Database Guide", "business    ", "1389", 19.99, 5000.00, 10, 4095, "An overview of available database systems with emphasis on common business applications. Illustrated.", new DateTime(1991, 06, 12), "409-56-7008", "Bennet", "Abraham");
			table.Rows.Add("BU1111", "Cooking with Computers: Surreptitious Balance Sheets", "business    ", "1389", 11.95, 5000.00, 10, 3876, "Helpful hints on how to use your electronic resources to the best advantage.", new DateTime(1991, 06, 09), "267-41-2394", "O'Leary", "Michael");
			table.Rows.Add("BU1111", "Cooking with Computers: Surreptitious Balance Sheets", "business    ", "1389", 11.95, 5000.00, 10, 3876, "Helpful hints on how to use your electronic resources to the best advantage.", new DateTime(1991, 06, 09), "724-80-9391", "MacFeather", "Stearns");
			table.Rows.Add("BU2075", "You Can Combat Computer Stress!", "business    ", "0736", 2.99, 10125.00, 24, 18722, "The latest medical and psychological techniques for living with the electronic office. Easy-to-understand explanations.", new DateTime(1991, 06, 30), "213-46-8915", "Green", "Marjorie");
			table.Rows.Add("BU7832", "Straight Talk About Computers", "business    ", "1389", 19.99, 5000.00, 10, 4095, "Annotated analysis of what computers can do for you: a no-hype guide for the critical user.", new DateTime(1991, 06, 22), "274-80-9391", "Straight", "Dean");
			table.Rows.Add("MC2222", "Silicon Valley Gastronomic Treats", "mod_cook    ", "0877", 19.99, 0.00, 12, 2032, "\"Favorite recipes for quick, easy and elegant meals.\"", new DateTime(1991, 06, 09), "712-45-1867", "del Castillo", "Innes");
			table.Rows.Add("MC3021", "The Gourmet Microwave", "mod_cook    ", "0877", 2.99, 15000.00, 24, 22246, "Traditional French gourmet recipes adapted for modern microwave cooking.", new DateTime(1991, 06, 18), "722-51-5454", "DeFrance", "Michel");
			table.Rows.Add("MC3021", "The Gourmet Microwave", "mod_cook    ", "0877", 2.99, 15000.00, 24, 22246, "Traditional French gourmet recipes adapted for modern microwave cooking.", new DateTime(1991, 06, 18), "899-46-2035", "Ringer", "Anne");
			table.Rows.Add("PC1035", "But Is It User Friendly?", "popular_comp", "1389", 22.95, 7000.00, 16, 8780, "\"A survey of software for the naive user focusing on the 'friendliness' of each.\"", new DateTime(1991, 06, 30), "238-95-7766", "Carson", "Cheryl");
			table.Rows.Add("PC8888", "Secrets of Silicon Valley", "popular_comp", "1389", 20.00, 8000.00, 10, 4095, "Muckraking reporting on the world's largest computer hardware and software manufacturers.", new DateTime(1994, 06, 12), "427-17-2319", "Dull", "Ann");
			table.Rows.Add("PC8888", "Secrets of Silicon Valley", "popular_comp", "1389", 20.00, 8000.00, 10, 4095, "Muckraking reporting on the world's largest computer hardware and software manufacturers.", new DateTime(1994, 06, 12), "846-92-7186", "Hunter", "Sheryl");
			table.Rows.Add("PC9999", "Net Etiquette", "popular_comp", "1389", null, null, null, null, "A must-read for computer conferencing.", new DateTime(2000, 08, 06), "486-29-1786", "Locksley", "Charlene");
			table.Rows.Add("PS1372", "Computer Phobic AND Non-Phobic Individuals: Behavior Variations", "psychology  ", "0877", 21.59, 7000.00, 10, 375, "\"A must for the specialist, this book examines the difference between those who hate and fear computers and those who don't.", new DateTime(1991, 10, 21), "724-80-9391", "MacFeather", "Stearns");
			table.Rows.Add("PS1372", "Computer Phobic AND Non-Phobic Individuals: Behavior Variations", "psychology  ", "0877", 21.59, 7000.00, 10, 375, "\"A must for the specialist, this book examines the difference between those who hate and fear computers and those who don't.", new DateTime(1991, 10, 21), "756-30-7391", "Karsen", "Livia");
			table.Rows.Add("PS2091", "Is Anger the Enemy?", "psychology  ", "0736", 10.95, 2275.00, 12, 2045, "Carefully researched study of the effects of strong emotions on the body. Metabolic charts included.", new DateTime(1991, 06, 15), "899-46-2035", "Ringer", "Anne");
			table.Rows.Add("PS2091", "Is Anger the Enemy?", "psychology  ", "0736", 10.95, 2275.00, 12, 2045, "Carefully researched study of the effects of strong emotions on the body. Metabolic charts included.", new DateTime(1991, 06, 15), "998-72-3567", "Ringer", "Albert");
			table.Rows.Add("PS2106", "Life Without Fear", "psychology  ", "0736", 7.00, 6000.00, 10, 111, "\"New exercise, meditation, and nutritional techniques that can reduce the shock of daily interactions. Popular audience. Sample menus included exercise video available separately.\"", new DateTime(1991, 10, 05), "998-72-3567", "Ringer", "Albert");
			table.Rows.Add("PS3333", "Prolonged Data Deprivation: Four Case Studies", "psychology  ", "0736", 19.99, 2000.00, 10, 4072, "What happens when the data runs dry?  Searching evaluations of information-shortage effects.", new DateTime(1991, 06, 12), "172-32-1176", "White", "Johnson");
			table.Rows.Add("PS7777", "Emotional Security: A New Algorithm", "psychology  ", "0736", 7.99, 4000.00, 10, 3336, "Protecting yourself and your loved ones from undue emotional stress in the modern world. Use of computer and nutritional aids emphasized.", new DateTime(1991, 06, 12), "486-29-1786", "Locksley", "Charlene");
			table.Rows.Add("TC3218", "\"Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean\"", "trad_cook   ", "0877", 20.95, 7000.00, 10, 375, "\"Profusely illustrated in color, this makes a wonderful gift book for a cuisine-oriented friend.", new DateTime(1991, 10, 21), "807-91-6654", "Panteley,Sylvia");
			table.Rows.Add("TC4203", "Fifty Years in Buckingham Palace Kitchens", "trad_cook   ", "0877", 11.95, 4000.00, 14, 15096, "\"More anecdotes from the Queen's favorite cook describing life among English royalty. Recipes techniques, tender vignettes.\"", new DateTime(1991, 06, 12), "648-92-1872", "Blotchet-Halls", "Reginald");
			table.Rows.Add("TC7777", "\"Sushi, Anyone?\"", "trad_cook   ", "0877", 14.99, 8000.00, 10, 4095, "Detailed instructions on how to make authentic Japanese sushi in your spare time.", new DateTime(1991, 06, 12), "267-41-2394", "O'Leary", "Michael");
			table.Rows.Add("TC7777", "\"Sushi, Anyone?\"", "trad_cook   ", "0877", 14.99, 8000.00, 10, 4095, "Detailed instructions on how to make authentic Japanese sushi in your spare time.", new DateTime(1991, 06, 12), "472-27-2349", "Gringlesby", "Burt");
			table.Rows.Add("TC7777", "\"Sushi, Anyone?\"", "trad_cook   ", "0877", 14.99, 8000.00, 10, 4095, "Detailed instructions on how to make authentic Japanese sushi in your spare time.", new DateTime(1991, 06, 12), "672-71-3249", "Yokomoto", "Akiko");

			return dataset;
		}

		private static SchemaDefinition PrepareSchema()
		{
			SchemaBuilder sb = new SchemaBuilder("TestingPubsTitles");

			sb.AddQueryFields(
				"title_id", "title", "type", "pub_id", "price", "advance",
				"royalty", "ytd_sales", "notes", "pubdate_str", "au_id", "au_lname", "au_fname",
				new QueryFieldBuilder("PubDate")
					.SetExpression<DateTime>("=CDate(Fields!pubdate_str.Value)"),
				new QueryFieldBuilder("PubDate_Year")
					.SetExpression<int>("=Year(Fields!PubDate.Value)"),
				new QueryFieldBuilder("PubDate_Quarter")
					.SetExpression<int>("=DatePart(DateInterval.Quarter, Fields!PubDate.Value)"),
				new QueryFieldBuilder("PubDate_Month")
					.SetExpression<int>("=Month(Fields!PubDate.Value)"),
				new QueryFieldBuilder("PubDate_Day")
					.SetExpression<int>("=Day(Fields!PubDate.Value)")
				);

			AttributeBuilder defaultField1 = new AttributeBuilder("title")
				.SetCaption("Book Title")
				.SetExpression<string>("=Fields!title.Value");

			AttributeBuilder defaultField2 = new HierarchyBuilder("Pub Date")
				.AddLevel(
				new LevelBuilder("Year")
					.SetExpression<int>("=Fields!PubDate_Year.Value")
					.SetCaptionExpression("=Fields!PubDate_Year.Value")
				)
				.AddLevel(
				new LevelBuilder("Quarter")
					.SetExpression<int>("=Fields!PubDate_Quarter.Value")
					.SetCaptionExpression("=\"Q\" & Fields!PubDate_Quarter.Value & \" \" & Fields!PubDate_Year.Value")
				)
				.AddLevel(
				new LevelBuilder("Month")
					.SetExpression<int>("=Fields!PubDate_Month.Value")
					.SetCaptionExpression("=MonthName(Fields!PubDate_Month.Value) & \", \" & Fields!PubDate_Year.Value")
				)
				.AddLevel(
				new LevelBuilder("Day")
					.SetExpression<int>("=Fields!PubDate_Day.Value")
					.SetCaptionExpression(
					"=MonthName(Fields!PubDate_Month.Value) & \" \" & Fields!PubDate_Day.Value & \", \" & Fields!PubDate_Year.Value")
				);

			sb.AddDimensions(
				new MeasuresDimBuilder("Measures")
					.AddField(
					new MeasureBuilder("Order count")
						.SetAggregateFunction(AggregateFunction.Count)
						.SetExpression<int>("=Fields!title_id.Value")
						.SetDefaultFormat("#0")
					)
					.AddField(
					new MeasureBuilder("Unit Price")
						.SetExpression<float>("=Fields!price.Value")
						.SetAggregateFunction(AggregateFunction.Avg)
						.SetFontFlags(FontFlags.Italic)
					)
					.AddField(
					new MeasureBuilder("Total Cost")
						.SetExpression<float>("=Fields!price.Value")
						.SetLocale("en-US")
						.SetDefaultFormat("c")
					)
					.AddFields(
					new MeasureBuilder("Advance")
						.SetExpression<float>("=Fields!advance.Value"),
					new MeasureBuilder("Royalty")
						.SetExpression<float>("=Fields!royalty.Value")
					),
				new AttributesDimBuilder("Dimension")
					.SetCaption("Other fields")
					.SetDefaultField(
					defaultField1)
					.AddField(defaultField1)
					.AddField(
					new AttributeBuilder("type")
						.SetCaption("Type")
						.SetExpression<string>("=Fields!type.Value")
					)
					.AddField(
					new AttributeBuilder("author")
						.SetCaption("Author name")
						.SetExpression<string>("=Fields!au_id.Value")
						.SetCaptionExpression("=Fields!au_lname.Value & \" \" & Fields!au_fname.Value")
					),
				new AttributesDimBuilder("Pub Date")
					.SetCaption("Date")
					.SetDefaultField(defaultField2
					)
					.AddFields(
					defaultField2,
					new AttributeBuilder("Date").SetExpression<DateTime>("=Fields!PubDate.Value"),
					new AttributeBuilder("Year").SetExpression<int>("=Fields!PubDate_Year.Value"),
					new AttributeBuilder("Quarter").SetExpression<int>("=Fields!PubDate_Quarter.Value"),
					new AttributeBuilder("Month").SetExpression<int>("=Fields!PubDate_Month.Value"),
					new AttributeBuilder("Day").SetExpression<int>("=Fields!PubDate_Day.Value")
					)
				);

			return sb.BuildSchema();
		}

		#endregion
	}

	/// <summary>
	/// Implements instance of data source associated with a name
	/// </summary>
	internal sealed class NamedDataSource
	{
		private const string TagName = "Name";
		private const string TagDataSource = "DataSource";

		private readonly string _name;
		private object _tag;
		private readonly IDataSource _instance;

		/// <summary>
		/// Gets the data source instance
		/// </summary>
		public IDataSource Instance
		{
			get { return _instance; }
		}

		/// <summary>
		/// Gets or sets user specific information related to this data source instance
		/// </summary>
		public object Tag
		{
			get { return _tag; }
			set { _tag = value; }
		}

		/// <summary>
		/// Gets the name
		/// </summary>
		public string Name
		{
			get { return _name; }
		}

		public NamedDataSource(XmlReader reader)
		{
			if (reader == null)
				throw new ArgumentNullException("reader");

			_name = reader.ReadElementString(TagName);
			_instance = DataSourceFactory.ReadDataSource(reader, TagDataSource);
		}

		public NamedDataSource(string name, IDataSource instance)
		{
			if (instance == null)
				throw new ArgumentNullException("instance");
			if (string.IsNullOrEmpty(name))
				throw new ArgumentNullException("name");

			_name = name;
			_instance = instance;
		}

		public void Write(XmlWriter writer)
		{
			writer.WriteElementString(TagName, _name);
			DataSourceFactory.WriteDataSource(_instance, writer, TagDataSource);
		}
	}

	/// <summary>
	/// Allows to operate with shared data sources.
	/// </summary>
	public static class DataSourceManager
	{
		private static readonly object _mutex = new object();

		/// <summary>
		/// Updates the data source node of the PivotView document with a shared version.
		/// </summary>
		/// <param name="doc">The document to be updated.</param>
		/// <param name="dataSource">The data source to be saved.</param>
		public static void UpdateDataSourceNode(XmlDocument doc, IDataSource dataSource)
		{
			string sharedName = Catalog.GetDataSourceName(dataSource);
			if (string.IsNullOrEmpty(sharedName))
				return;
			XmlNode dataSourceNode = doc.SelectSingleNode("//DataSource");
			dataSourceNode.RemoveAll();
			dataSourceNode.AppendChild(doc.CreateElement("DataSourceType")).InnerText = 
				// support for old shared data source named conventions
				(dataSource is IMultiplePerspectives) ? "SharedMP" :
					(dataSource is IPersistLocalCube) ? ((dataSource is LocalCubeDataSource) ? "SharedLocalCube" : "SharedLC") :
						"Shared";
			dataSourceNode.AppendChild(doc.CreateElement("Settings"));
			dataSourceNode.LastChild.AppendChild(doc.CreateElement("Name")).InnerText = sharedName;
			IMultiplePerspectives multiplePerspectives = dataSource as IMultiplePerspectives;
			if (multiplePerspectives != null)
			{
				if (! dataSource.IsConnected)
					dataSource.Connect();
				dataSourceNode.LastChild.AppendChild(doc.CreateElement("Cube")).InnerText = multiplePerspectives.Cube;
			}
		}

		/// <summary>
		/// Resolves a shared data source or reads a built-in.
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public static IDataSource ReadDataSource(XmlNode node)
		{
			if (node == null)
				throw new ArgumentNullException("node");

			IDataSource dataSource;
			XmlNode dataSourceNode = node.SelectSingleNode("//DataSource") ?? node;
			XmlNode dataSourceTypeNode = dataSourceNode["DataSourceType"];
			string[] oldSharedNames = new string[] { "Shared", "SharedLC", "SharedLocalCube", "SharedMP" };
			if (dataSourceTypeNode != null && Array.IndexOf(oldSharedNames, dataSourceTypeNode.InnerText) >= 0)
			{
				// Load a shared data source.
				XmlNode dataSourceNameNode = dataSourceNode.SelectSingleNode("Settings/Name");
				string name = dataSourceNameNode.InnerText;
				dataSource = Catalog.Resolve(name);
				// Apply cube
				IMultiplePerspectives multiplePerspectives = dataSource as IMultiplePerspectives;
				if (multiplePerspectives != null)
				{
					if (! dataSource.IsConnected)
						dataSource.Connect();
					if (multiplePerspectives.Cubes.Count > 0)
					{
						string cube = multiplePerspectives.Cubes[0];
						XmlNode cubeNode = dataSourceNode.SelectSingleNode("Settings/Cube");
						if (cubeNode != null)
							cube = cubeNode.InnerText;
						multiplePerspectives.Cube = cube;
					}
				}
			}
			else
			{
				// Load a built-in data source.
				XmlNodeReader dataSourceReader = new XmlNodeReader(dataSourceNode);
				dataSource = DataSourceFactory.ReadDataSource(dataSourceReader, "DataSource");
			}
			if (dataSource == null)
			{
				throw new DataSourceException("Failed to read data source settings.");
			}
			return dataSource;
		}

		/// <summary>
		/// Gets the data source catalog.
		/// </summary>
		/// <value>The data source catalog.</value>
		public static DataSourceCatalog Catalog
		{
			get { return DataSourceCatalog.GetInstance(DefaultFolder); }
		}

		private static string _defaultFolder;

		/// <summary>
		/// Folder where the data source catalog is placed.
		/// </summary>
		internal static string DefaultFolder
		{
			set
			{
				lock (_mutex)
				{
					if (string.IsNullOrEmpty(value))
						throw new ArgumentNullException("value");
					_defaultFolder = value;
				}
			}
			get
			{
				lock (_mutex)
				{
					if (string.IsNullOrEmpty(_defaultFolder))
					{
						_defaultFolder = System.Configuration.ConfigurationManager.AppSettings["DataSourcesPath"];
					}
					return _defaultFolder;
				}
			}
		}
	}
}